#include <eku/stdio.h>
#include <eku/stdlib.h>
#include <eku/system.h>
#include <eku/sync.h>
#include <eku/proc.h>
#include <eku/symbol.h>
#include <inc/error.h>
#include <inc/mmu.h>

static int irq_ap;
static int timer_process_pid;
static volatile const uint32_t *_timer_counter;
static uint32_t _timer_freq;
static void(*set_timer)(uint32_t);

#define TIMER_SIZE 1024

static struct timer_t
{
	 union
	 {
		  struct
		  {
			   int      pid;
			   int      status;
			   union
			   {
					int heap_idx;
					int close_next;
			   };
			   uint32_t tp;
		  };
		  int free_next;
	 };	 
} timers[TIMER_SIZE];

#define TIMER_STATUS_FREE   0
#define TIMER_STATUS_OPEN   1
#define TIMER_STATUS_CLOSED 2

static int timer_count;
static int timer_heap[TIMER_SIZE + 1];
static int free_head;

static uint32_t old_counter;
static spin_lock_t timer_heap_lock;
static spin_lock_t timer_alloc_lock;


/* HPET ======================================================= */

#define HR_GEN_CAP_ID 0
#define HR_GEN_CONF   2
#define HR_IRQ_STATUS 4
#define HR_MAIN_COUNTER_VALUE 30

#define HRT_BASE(n)    ((n << 2) + 32)
#define HRT_CONF(n)    (HRT_BASE(n) + 0)
#define HRT_COMP(n)    (HRT_BASE(n) + 1)
#define HRT_FSB_INT(n) (HRT_BASE(n) + 2)

static volatile uint64_t *hpet_regs;
static uint32_t hpet_period;
static int      hpet_timer;
static volatile uint32_t *hpet_counter;

#define TP_AFTER_NS(ns) ((uint32_t)(*_timer_counter + (uint32_t)(0.000000001 * _timer_freq * ns)))
#define TP_AFTER_US(us) ((uint32_t)(*_timer_counter + (uint32_t)(0.000001 * _timer_freq * us)))
#define TP_AFTER_MS(ms) ((uint32_t)(*_timer_counter + (uint32_t)(0.001 * _timer_freq * ms)))

static void
hpet_set_timer(uint32_t tp)
{
	 hpet_regs[HRT_COMP(hpet_timer)] = tp;
}

static int
hpet_init(void)
{
	 physaddr_t hpet_phys = 0xFED00000;
	 if (sysconf.hpet_phys == 0)
		  return -1;
	 else hpet_phys = sysconf.hpet_phys;	 
	 hpet_regs = (volatile uint64_t *)vmalloc(PAGE_SIZE, hpet_phys);

	 uint32_t id_hi = hpet_regs[HR_GEN_CAP_ID] >> 32;
	 uint32_t id_lo = hpet_regs[HR_GEN_CAP_ID];
	 
	 printf("HPET INFO[%08x %08x]:\n\trev_id 0x%02x timers: %d width: %d period: %d (10^-15 s)\n",
			id_hi, id_lo,
			id_lo & 0xff, (id_lo >> 8) & 0xf, (id_lo & (1 << 13)) ? 64 : 32, id_hi);
	 // hpet_counter_width = (id_lo & (1 << 13)) ? 64 : 32;
	 hpet_period = id_hi;
	 hpet_timer = (id_lo >> 8) & 0xf;

	 /* Set the configuration to enable CNF and disable legacy replacement */
	 hpet_regs[HR_GEN_CONF] = 1;

	 uint64_t id = hpet_regs[HRT_CONF(hpet_timer)];
	 /* disable fsb */
	 id &= ~((uint64_t)1 << 14);
	 /* force 32-bit mode */
	 if (id & ((uint64_t)1 << 4))
		  id &= ~((uint64_t)1 << 3);
	 /* enable the interrupt 2, edge triggered */
	 id &= ~((uint64_t)0x1f << 9);
	 id |=  ((uint64_t)2 << 9);
	 
	 id |= ((uint64_t)1 << 2);
	 id &= ~((uint64_t)1 << 1);
	 hpet_regs[HRT_CONF(hpet_timer)] = id;
	 hpet_counter = (uint32_t *)(hpet_regs + HR_MAIN_COUNTER_VALUE);
	
	 /* Listen on irq 2 */
	 irq_ap = sysconf.irq_ap[2];
	 _timer_counter = hpet_counter;
	 _timer_freq = 1000000000000000.0 / hpet_period;
	 set_timer = &hpet_set_timer;

	 printf("HPET FREQ = %d\n", _timer_freq);
	 
	 return 0;
}

/* SOFT TIMER ================================================= */

static spin_lock_t measure_lock;

static void
st_measure_process(void *_arg)
{
	 ips_recv(sysconf.irq_ap[2], 0);
	 ips_recv_break(0);
	 ips_recv_try(0, 0, 0);
	 
	 asm volatile("cli");
	 outb(0x43, 0x34);
	 outb(0x40, 0x00);
	 outb(0x40, 0x40);
	 asm volatile("sti");

	 wait_pretend();
	 uint32_t old = sysconf.jiffies_0;
	 set_soft_timer(sysconf.jiffies_0 + 2000, cur_pid());
	 wait_try();

	 uint64_t _delta = 0;
	 ips_recv(sysconf.irq_ap[2], 0);
	 ips_recv_try(0, 0, &_delta);
	 ips_recv_break(0);
	 ips_recv_try(0, 0, 0);

	 /* DISABLE PIT */
	 asm volatile("cli");
	 outb(0x43,0x04);
	 uint32_t lo=inb(0x40);
	 uint32_t hi=inb(0x40);
	 outb(0x43, 0x30);
	 outb(0x40, 0x00);
	 outb(0x40, 0x00);
	 asm volatile("sti");

	 uint32_t delta = ((_delta << 14) + (hi << 8) + lo);

	 _timer_freq = (sysconf.jiffies_0 - old) / (delta / 1193180.0);
	 spl_release(&measure_lock);

	 printf("measure finished, delta = %d, time = %d, %d, freq = %d\n", delta, (uint32_t)(delta / 1193180.0), sysconf.jiffies_0 - old, _timer_freq);
}

static void
st_set_timer(uint32_t tp)
{
	 set_soft_timer(tp, cur_pid());
}

static int
st_init(void)
{
	 if (sysconf.use_pic)
	 {
		  printf("use PIT FREQ as soft timer freq\n");
		  _timer_freq = PIT_FREQ;
	 }
	 else
	 {
		  printf("measure the soft clock freq ...\n");
		  spl_acquire(&measure_lock);
		  process_create(st_measure_process, (void *)cur_pid(), 8192);
		  spl_acquire(&measure_lock);
	 }

	 _timer_counter = &sysconf.jiffies_0;
	 irq_ap = -1;
	 set_timer = &st_set_timer;
	 
	 return 0;
}

/* ============================================================ */

static int
_timer_open(int pid, uint32_t tp)
{
	 spl_acquire(&timer_alloc_lock);
	 
	 int result = free_head;
	 if (result != -1)
		  free_head = timers[result].free_next;
	 
	 spl_release(&timer_alloc_lock);

	 if (result == -1) return -1;
	 timers[result].status = TIMER_STATUS_OPEN;

	 spl_acquire(&timer_heap_lock);

	 timers[result].heap_idx = ++ timer_count;
	 timers[result].pid      = pid;
	 timers[result].tp       = tp;
	 
	 int cur = timers[result].heap_idx;
	 while (cur > 1)
	 {
		  int comp = cur >> 1;
		  if ((timers[timer_heap[comp]].tp - old_counter) >
			  (tp - old_counter))
		  {
			   timer_heap[cur] = timer_heap[comp];
			   timers[timer_heap[cur]].heap_idx = cur;
			   
			   cur = comp;
		  }
		  else break;
	 }

	 timer_heap[cur] = result;
	 timers[result].heap_idx = cur;

	 set_timer(timers[timer_heap[1]].tp);
	 if ((timers[timer_heap[1]].tp - *_timer_counter) > 0x80000000)
		  notify(timer_process_pid);

	 spl_release(&timer_heap_lock);

	 return result;
}

static int
_timer_open_ns(int pid, uint32_t ns)
{
	 return _timer_open(pid, TP_AFTER_NS(ns));
}

static int
_timer_open_us(int pid, uint32_t us)
{
	 return _timer_open(pid, TP_AFTER_US(us));
}

static int
_timer_open_ms(int pid, uint32_t ms)
{
	 return _timer_open(pid, TP_AFTER_MS(ms));
}

static void
_timer_close_unsafe(int idx)
{
	 int cur = timers[idx].heap_idx;
	 uint32_t tp = timers[idx].tp;
	 int rep = timer_heap[cur] = timer_heap[timer_count --];
	 timers[idx].status = TIMER_STATUS_CLOSED;
	 if (timer_count == 0) return;

	 while (1)
	 {
		  int comp = cur << 1;
		  if (comp > timer_count) break;
		  if (comp < timer_count &&
			  ((timers[timer_heap[comp]].tp - old_counter) >
			   (timers[timer_heap[comp + 1]].tp - old_counter)))
			   ++ comp;
		  
		  if ((timers[timer_heap[comp]].tp - old_counter) <
			  (tp - old_counter))
		  {
			   timer_heap[cur] = timer_heap[comp];
			   timers[timer_heap[cur]].heap_idx = cur;
			   
			   cur = comp;
		  }
		  else break;
	 }

	 timer_heap[cur] = rep;
	 timers[rep].heap_idx = cur;
}

static void
_timer_close(int idx)
{
	 spl_acquire(&timer_heap_lock);
	 if (timers[idx].status == TIMER_STATUS_OPEN)
		  _timer_close_unsafe(idx);
	 spl_release(&timer_heap_lock);
}

static int
_timer_test(int idx)
{
	 return timers[idx].status == TIMER_STATUS_OPEN;
}

static int
_timer_free(int idx)
{
	 if (timers[idx].status == TIMER_STATUS_CLOSED)
	 {
		  spl_acquire(&timer_alloc_lock);
		  timers[idx].status = TIMER_STATUS_FREE;
		  timers[idx].free_next = free_head;
		  free_head = idx;
		  spl_release(&timer_alloc_lock);

		  return 0;
	 }
	 else return 1;
}

static void
timer_process(void *_arg)
{
	 while (1)
	 {
		  int wait = 1;
		  int close_head = -1;
		  uint32_t cur_count = *_timer_counter;

		  spl_acquire(&timer_heap_lock);
		  while (timer_count > 0 &&
				 (timers[timer_heap[1]].tp - *_timer_counter) > 0x80000000)
		  {
			   int idx = timer_heap[1];
			   _timer_close_unsafe(idx);
			   
			   timers[idx].close_next = close_head;
			   close_head = idx;
		  }

		  if (timer_count > 0)
		  {
			   set_timer(timers[timer_heap[1]].tp);
			   if ((timers[timer_heap[1]].tp - (old_counter = *_timer_counter)) > 0x80000000)
					wait = 0;
		  }
		  else wait_pretend();
		  spl_release(&timer_heap_lock);

		  while (close_head != -1)
		  {
			   /* printf("timer %d closed, notify pid %d, ?? %d\n", */
			   /* 		  close_head, timers[close_head].pid); */
			   notify(timers[close_head].pid);
			   close_head = timers[close_head].close_next;
		  }

		  if (wait)
		  {
			   if (irq_ap != -1)
			   {
					/* Tricky to ensure all resource can be reused */
					ips_recv(irq_ap, 0);
					ips_recv_try(0, IPS_WAIT_INT, 0);
					ips_recv_break(0);
					ips_recv_try(0, 0, 0);
			   }
			   else
			   {
					wait_try();
			   }
		  }
	 }
}

static int
module_load(void)
{
	 if (hpet_init())
	 {
	 	  if (st_init())
			   return -1;
	 }

	 spl_init(&timer_heap_lock);
	 spl_init(&timer_alloc_lock);
   
	 timer_count = 0;
	 int i;
	 for (i = 0; i != TIMER_SIZE; ++ i)
	 {
		  timers[i].free_next = i + 1;
		  timers[i].status = TIMER_STATUS_FREE;
	 }
	 timers[TIMER_SIZE - 1].free_next = -1;
	 free_head = 0;
	 
	 timer_process_pid = process_create(timer_process, NULL, 8192);

	 export_symbol("timer_open",      &_timer_open, 1);
	 export_symbol("timer_open_ns",   &_timer_open_ns, 1);
	 export_symbol("timer_open_us",   &_timer_open_us, 1);
	 export_symbol("timer_open_ms",   &_timer_open_ms, 1);
	 export_symbol("timer_close",     &_timer_close, 1);
	 export_symbol("timer_test",      &_timer_test, 1);
	 export_symbol("timer_free",      &_timer_free, 1);
	 export_symbol("timer_counter",   (void *)_timer_counter, 1);
	 export_symbol("timer_freq",      &_timer_freq, 1);

	 return 0;
}

static int
module_unload(void)
{
	 return 0;
}
